/* 2014/8/17 */
package com.cosmos.quartz.launch;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.SimpleTimeZone;

import org.apache.log4j.Logger;
import org.quartz.DateBuilder;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;

public class AgentLauncher {
	
	private static final Logger log = Logger.getLogger(AgentLauncher.class.getSimpleName());
	
	private static boolean isLaunched = false;
	
	private static Scheduler scheduler;
	
	private static Map<String, JobKey> jobMap;
	
	private static Map<String, TriggerKey> triggerMap;
	
	private static Map<String, String> jobTriggerMap;
	
	public static void main(String[] args) {
		
		if (args.length == 0) {
			log.error("No launch configuration file specified.");
			return;
		}
		
		LaunchConfig launchConfig = loadConfig(args[0]);
		if (launchConfig == null) {
			return;
		}
		
		if (launchConfig.getLog4j() != null) {
			org.apache.log4j.PropertyConfigurator.configure(launchConfig.getLog4j());
			log.debug("Set Log4j properties file: " + launchConfig.getLog4j());
		}
		
		if (launchConfig.getSystemProperties() != null) {
			try {
				for (SystemPropertyConfig systemPropertyConfig : launchConfig.getSystemProperties()) {
					System.setProperty(systemPropertyConfig.getKey(), systemPropertyConfig.getValue());
					log.debug("Set system property key: " + systemPropertyConfig.getKey() + ", value: " + systemPropertyConfig.getValue());
				}
			} catch (Exception ex) {
				log.error("Set system property failed.", ex);
				return;
			}
		}
		
		if (launchConfig.getInitializers() != null) {
			for (InitializerConfig initializerConfig : launchConfig.getInitializers()) {
				try {
					Class<?> initializerClass = Class.forName(initializerConfig.getClassName());
					log.debug("Initializer class: " + initializerConfig.getClassName());
					Initializer initializer = (Initializer)initializerClass.newInstance();
					initializer.initialize();
				} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException ex) {
					log.error("Initialize agent failed.", ex);
					return;
				}
			}
			log.debug("Initialize agent completed.");
		}

		if (launchConfig.getJobs() != null) {
			log.info("Initialize scheduler...");
			
			jobMap = new HashMap<>();
			triggerMap = new HashMap<>();
			jobTriggerMap = new HashMap<>();
			
			try {
				scheduler = StdSchedulerFactory.getDefaultScheduler();
				
				for (JobConfig jobConfig : launchConfig.getJobs()) {					
					JobDetailConfig jobDetailConfig = jobConfig.getJobDetail();
					TriggerConfig triggerConfig = jobConfig.getTrigger();
					TriggerIntervalConfig intervalConfig = triggerConfig.getInterval();
					TriggerStartConfig startConfig = triggerConfig.getStart();
					if (jobDetailConfig == null || triggerConfig == null || intervalConfig == null) {
						log.debug("Withdraw uncompleted job.");
						continue;
					}
					
					Class<?> jobClass = Class.forName(jobDetailConfig.getClassName());
					log.debug("Job class: " + jobDetailConfig.getClassName());
					
					@SuppressWarnings("unchecked")
					JobBuilder jobBuilder = JobBuilder.newJob((Class<? extends Job>)jobClass);					
					if (jobDetailConfig.getIdName() != null && jobDetailConfig.getIdGroup() != null) {
						jobBuilder.withIdentity(jobDetailConfig.getIdName(), jobDetailConfig.getIdGroup());
						log.debug("Job name: " + jobDetailConfig.getIdName() + ", group: " + jobDetailConfig.getIdGroup());
					}
					
					JobDetail job = jobBuilder.build();
					//jobMap.put(job.getKey().getName(), job.getKey());
					
					TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
					if (triggerConfig.getIdName() != null && triggerConfig.getIdGroup() != null) {
						triggerBuilder.withIdentity(triggerConfig.getIdName(), triggerConfig.getIdGroup());
						log.debug("Trigger name: " + triggerConfig.getIdName() + ", group: " + triggerConfig.getIdGroup());
					}
					
					if (startConfig != null) {
						DateBuilder startDateBuilder = null;
						if (startConfig.getTimeZoneOffset() != null) {
							int timeZoneOffset = startConfig.getTimeZoneOffset();
							startDateBuilder = DateBuilder.newDateInTimezone(
									new SimpleTimeZone(timeZoneOffset, Integer.toString(timeZoneOffset)));
							log.debug("Trigger start time zone offset: " + startConfig.getTimeZoneOffset());
						} else {
							startDateBuilder = DateBuilder.newDate();
						}
						
						int hour = 0;
						if (startConfig.getHour() != null) {
							hour = startConfig.getHour();
						}
						int minute = 0;
						if (startConfig.getMinute() != null) {
							minute = startConfig.getMinute();
						}
						int second = 0;
						if (startConfig.getSecond() != null) {
							second = startConfig.getSecond();
						}
						startDateBuilder.atHourMinuteAndSecond(hour, minute, second);
						log.debug("Trigger start time: " + hour + ":" + minute + ":" + second);
						
						triggerBuilder.startAt(startDateBuilder.build());
					} else {
						triggerBuilder.startNow();
						log.debug("Trigger start now");
					}
					
					SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule();
					if (intervalConfig.getHours() != null) {
						schedBuilder.withIntervalInHours(intervalConfig.getHours());
						log.debug("Trigger interval hour: " + intervalConfig.getHours());
					}
					if (intervalConfig.getMinutes() != null) {
						schedBuilder.withIntervalInMinutes(intervalConfig.getMinutes());
						log.debug("Trigger interval minute: " + intervalConfig.getMinutes());
					}
					if (intervalConfig.getSeconds() != null) {
						schedBuilder.withIntervalInSeconds(intervalConfig.getSeconds());
						log.debug("Trigger interval second: " + intervalConfig.getSeconds());
					}
					if (intervalConfig.getMilliseconds() != null) {
						schedBuilder.withIntervalInMilliseconds(intervalConfig.getMilliseconds());
						log.debug("Trigger interval millisecond: " + intervalConfig.getMilliseconds());
					}
					if (intervalConfig.isRepeatForever() == null || intervalConfig.isRepeatForever()) {
						schedBuilder.repeatForever();
						log.debug("Trigger repeat forever.");
					}
					triggerBuilder.withSchedule(schedBuilder);
											
					Trigger trigger = triggerBuilder.build();
					//triggerMap.put(trigger.getKey().getName(), trigger.getKey());
						
					scheduler.scheduleJob(job, trigger);
				}
				
				addShutDownHook();
				
				scheduler.start();
				
				isLaunched = true;
				log.info("Initialize scheduler completed.");
			} catch (Exception ex) {
				log.error("Initialize scheduler failed.", ex);
				shutdownScheduler();
			}			
		}
	}

	/**
	 * Returns a value indicating whether launcher is completely started.
	 * @return
	 */
	public static boolean isLaunched() {
		return isLaunched;
	}
	
	/**
	 * Returns scheduler of launcher.
	 * @return
	 */
	public static Scheduler getScheduler() {
		return scheduler;
	}
	
	/**
	 * Returns a map whose key is job name and value is JobKey object.
	 * @return
	 */
	public static Map<String, JobKey> getJobMap() {
		return jobMap;
	}

	/**
	 * Returns a map whose key is trigger name and value is TriggerKey object.
	 * @return
	 */
	public static Map<String, TriggerKey> getTriggerMap() {
		return triggerMap;
	}
	
	/**
	 * Returns a map whose key is job name and value is trigger name.
	 * @return
	 */
	public static Map<String, String> getJobTriggerMap() {
		return jobTriggerMap;
	}

	/*private static void saveConfig(String launchConfigFilePath) {
		LaunchConfig launch = new LaunchConfig();
		
		launch.setLog4j("log4j.properties");
		
		SystemPropertyConfig systemProperty = new SystemPropertyConfig();
		systemProperty.setKey("System key");
		systemProperty.setValue("System value");
		//launch.setSystemProperties(new SystemPropertyConfig[] {systemProperty});

		JobDetailConfig jobDetail = new JobDetailConfig();
		jobDetail.setClassName("com.mtp.agent.PrintTimeJob");
		jobDetail.setIdName("PrintTimeJob");
		jobDetail.setIdGroup("TimeGroup");		
		
		TriggerConfig trigger = new TriggerConfig();
		trigger.setSeconds(5);
		trigger.setRepeatForever(true);
		trigger.setIdName("TripleSecondsTrigger");
		trigger.setIdGroup("SecondsTriggerGroup");
		
		JobConfig job = new JobConfig();
		job.setJobDetail(jobDetail);
		job.setTrigger(trigger);
		launch.setJobs(new JobConfig[] {job});
		
		try {
			LaunchConfigSerializer.save(launch, launchConfigFilePath);
		} catch (IOException ex) {
			log.error(ex);
		}
	}*/
	
	private static LaunchConfig loadConfig(String launchConfigFilePath) {
		LaunchConfig launch = null;
		
		try {
			launch = LaunchConfigSerializer.load(launchConfigFilePath);
		} catch (FileNotFoundException ex) {
			log.error(ex);
		} catch (IOException ex) {
			log.error(ex);
		}
		
		return launch;
	}
	
	private static void shutdownScheduler() {
		log.info("Shutdown scheduler...");
		try {
			if (scheduler != null) {
				scheduler.shutdown(true);
			}					
		} catch (SchedulerException ex) {
			log.error("Shutdown scheduler failed.", ex);
		}
		log.info("Shutdown scheduler completed.");		
	}
	
	/**
	 * Register the JVM hook to safely shutdown scheduler.
	 */
	private static void addShutDownHook() {
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
			
			@Override
			public void run() {
				shutdownScheduler();
			}
		}));
	}
}
